package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.common.PageResult;
import cn.itcast.hotel.dto.HotelPageQueryDTO;
import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.service.HotelService;
import cn.itcast.hotel.vo.HotelVO;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 酒店服务
 *
 * @author liudo
 * @date 2023/08/15
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class HotelServiceImpl extends ServiceImpl<HotelMapper, Hotel> implements HotelService {

    private final RestHighLevelClient restHighLevelClient;

    /**
     * 列表页
     *
     * @param hotelPageQueryDTO 酒店页面查询DTO
     * @return {@link PageResult}<{@link HotelVO}>
     */
    @Override
    public PageResult<HotelVO> listPage(HotelPageQueryDTO hotelPageQueryDTO) {
        String key = hotelPageQueryDTO.getKey();
        int page = Objects.isNull(hotelPageQueryDTO.getPage()) ? 1 : hotelPageQueryDTO.getPage();
        int size = Objects.isNull(hotelPageQueryDTO.getSize()) ? 20 : hotelPageQueryDTO.getSize();

        String city = hotelPageQueryDTO.getCity();
        String starName = hotelPageQueryDTO.getStarName();
        String brand = hotelPageQueryDTO.getBrand();
        Integer minPrice = hotelPageQueryDTO.getMinPrice();
        Integer maxPrice = hotelPageQueryDTO.getMaxPrice();
        String location = hotelPageQueryDTO.getLocation();


        try {
            int from = (page - 1) * size;
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            if (StringUtils.isNoneEmpty(key)) {
                boolQueryBuilder.must(QueryBuilders.matchQuery("all", key));
            } else {
                boolQueryBuilder.must(QueryBuilders.matchAllQuery());
            }
            if (StringUtils.isNoneEmpty(city)) {
                boolQueryBuilder.filter(
                        QueryBuilders.termQuery("city", city)
                );

            }
            if (StringUtils.isNoneEmpty(starName)) {
                boolQueryBuilder.filter(
                        QueryBuilders.termQuery("starName", starName)
                );

            }
            if (StringUtils.isNoneEmpty(brand)) {
                boolQueryBuilder.filter(
                        QueryBuilders.termQuery("brand", brand)
                );

            }
            if (Objects.nonNull(minPrice) && Objects.nonNull(maxPrice)) {
                boolQueryBuilder.filter(
                        QueryBuilders.rangeQuery("price")
                                .gte(minPrice)
                                .lte(maxPrice)
                );

            }

            SearchRequest request = new SearchRequest("hotel");

            request.source()
                    .query(
                            new FunctionScoreQueryBuilder(
                                    boolQueryBuilder,
                                    new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                                            new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                                    QueryBuilders.termQuery("brand", "如家"),
                                                    ScoreFunctionBuilders.weightFactorFunction(10)
                                            )
                                    }
                            ).boostMode(CombineFunction.SUM)
                    )
                    .from(from)
                    .size(size);

            if (StringUtils.isNoneEmpty(location)) {
                request.source()
                        .sort(
                                SortBuilders.geoDistanceSort("location", new GeoPoint(location))
                                        .unit(DistanceUnit.KILOMETERS)
                                        .order(SortOrder.ASC)
                        );

            }

            SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);
            SearchHit[] hits = search.getHits().getHits();
            List<HotelVO> hotelVOList = Arrays.stream(hits)
                    .map(hit -> {
                        String sourceAsString = hit.getSourceAsString();
                        HotelVO hotelVO = JSON.parseObject(sourceAsString, HotelVO.class);
                        Object[] sortValues = hit.getSortValues();
                        if (sortValues.length > 0) {
                            hotelVO.setDistance(sortValues[0]);
                        }
                        hotelVO.setAD(Objects.equals("如家", hotelVO.getBrand()));
                        return hotelVO;
                    }).collect(Collectors.toList());
            TotalHits totalHits = search.getHits().getTotalHits();
            long total = totalHits.value;

            PageResult<HotelVO> pageResult = new PageResult<>();
            pageResult.setTotal(total);
            pageResult.setData(hotelVOList);

            return pageResult;
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            e.printStackTrace();
        }

        return new PageResult<>();
    }
}
